这里的内容仅为读书笔记,如果您需要阅读原版书籍,请购买正版以支持原创。感谢您的理解和支持。
📌 度量监控。基于Spring Boot提供的Actuator组件,可以通过RESTful接口获取JVM性能指标、线程工作状态等运行时信息
📌 @RestController注解是传统Spring MVC中所提供的@Controller注解的升级版,相当于@Controller和@ResponseEntity注解的结合体,会自动使用JSON实现序列化/反序列化操作。
📌 配置文件可以是空的,开发人员如果不需要特别指定服务器端口的信息
📌 如果我们不希望在全局配置文件中指定所需要激活的Profile,而是想把这个过程延迟到运行这个服务时,那么可以直接在java -jar命令中添加--spring.profiles.active参数,
📌 Spring的依赖注入机制在解决循环依赖问题时采用了三级缓存机制
📌 相较Spring WebMVC,Spring WebFlux能够为我们实现异步、非阻塞的远程交互过程
📌 Spring内置的响应式编程框架是Project Reactor
📌 Spring的缓存机制非常灵活,提供了一组包括@Cacheable、@CachePut和@CacheEvict在内的注解来简化系统缓存的使用过程,可以通过这些注解对容器中的任意Bean或者Bean的方法添加缓存功能
📌 Spring Boot Actuator组件是承载系统监控功能的组件,通过一系列HTTP端点提供系统监控功能。
📌 Spring也提供了专门针对云原生架构的Spring Native框架。Spring Native基于GraalVM虚拟机技术,为开发人员提供了更快的启动时间以及更少的内存占用
📌 构造器注入能够保证注入的组件不可变,并且确保需要的依赖不为空
📌 在刚开始阅读Spring源码时,我建议你直接从AnnotationConfigApplicationContext的启动流程切入,这一流程位于它的构造函数中
📌 ListableBeanFactory是Spring中常用的一个BeanFactory,通过这个接口,我们可以一次获取多个Bean。
📌 所谓的三级缓存,在Spring中表现为三个Map对象
📌 singletonObjects变量就是第一级缓存,用来持有完整的Bean实例。而earlySingletonObjects中存放的是那些提前暴露的对象,也就是已经创建但还没有完成属性注入的对象,属于第二级缓存。最后的singletonFactories存放用来创建earlySingleton-Objects的工厂对象,属于第三级缓存。
📌 Spring解决循环依赖的诀窍就在于singletonFactories这个第三级缓存
📌 相信你也理解了为什么构造器注入无法解决循环依赖问题。这是因为构造器注入过程是发生在Bean初始化的第一个步骤createBeanInstance()中,而这个步骤还没有调用addSingletonFactory()方法完成第三级缓存的构建,自然也就无法从该缓存中获取目标对象
📌 消除循环依赖的基本思路也是这样,就是通过在两个相互循环依赖的组件之间添加中间层,变循环依赖为间接依赖。有三种方法可以做到这一点,分别是提取中介者、转移业务逻辑和引入回调
📌 提取中介者的核心思想是把两个相互依赖的组件中的交互部分抽象出来形成一个新的组件,而新组件同时包含着对原有两个组件的引用,这样就把循环依赖关系剥离出来并提取到一个专门的中介者组件中
📌 在Spring IoC容器中,Bean的默认作用域是单例,也就是说不管有多少个对Bean的引用,容器只会创建一个实例。而原型作用域则不同,每次请求Bean时,Spring IoC容器都会创建一个新的对象实例。
📌 从两种作用域的效果而言,我们总结出一条开发上的经验,即对于有状态的Bean,我们应该使用原型作用域,反之则应该使用单例作用域。
📌 在Spring中,我们可以通过设置组件扫描范围来简化Bean的注入配置。因为任何类都位于某一个包结构之下,所以Spring提供了一个@ComponentScan注解,该注解在需要大规模对象注入的场景下非常有用
📌 添加了@Lazy注解的效果是只有在使用到这个Bean时它才会去初始化,而不是在Spring IoC容器启动时直接初始化,这样就可以节省容器资源
📌 。和Proxy只能代理接口不同,Enhancer既能够代理接口,也能够代理普通类,但不能拦截final类和方法。
📌 @Scope注解还可以用来指定代理模式ScopedProxyMode
📌 代码清单3-22 execution()基本语法execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)这个语法看似复杂,但是我们逐个分解所有的模式,它们其实就是描述了一个方法的特征。■modifiers-pattern:表示方法的修饰符。■ret-type-pattern:表示方法的返回值。■declaring-type-pattern:表示方法所在的类的路径。■name-pattern:表示方法名。■param-pattern:表示方法的参数。■throws-pattern:表示方法抛出的异常。
📌 这是因为Spring AOP是通过代理实现的,而无论是JDK代理还是CGLIB代理,其运行机制是对某一个外部的接口或实现类进行代理,像上述代码中直接调用ServiceImpl类内的方法是不会应用代理的。
📌 不要在已经受Spring管理的Bean类上使用@Configurable注解,否则它将执行双重初始化,一次是通过Spring容器,一次是通过AOP切面。
📌 @Configurable这个注解的作用就是告诉Spring在构造函数运行之前将依赖关系注入对象中
📌 Spring的推荐做法是尽可能使用JDK动态代理而不是CGLIB代理
📌 常见的动态代理实现技术包括JDK自带的代理类、第三方的CGLIB和javassist。
📌 Spring Boot提供了一系列便捷有用的注解来简化对请求输入的控制过程,常用的包括上述UserController中所展示的@PathVariable和@RequestBody
📌 @PathVariable注解用于获取路径参数,即从类似url/{id}这种形式的路径中获取{id}参数的值
📌 在HTTP中,content-type属性用来指定所传输的内容类型。而我们可以通过@Request-Mapping注解中的produces属性来对其进行设置,通常会将其设置为application/json
📌 InterceptingHttpAccessor应该包含两部分处理功能,一部分是设置和管理请求拦截器ClientHttpRequestInterceptor,另一部分则是获取用于创建客户端HTTP请求的工厂类ClientHttpRequestFactory
📌 通过RestTemplate的类层结构,我们可以理解它的设计思想。整个类层结构可以清晰地分成两条线,左边部分用于完成与HTTP请求相关的实现机制,而右边部分则提供了RESTful风格的操作入口,并使用了面向对象的接口和抽象类完成了对这两部分功能的聚合。
📌 我们已经知道什么是多媒体(Multimedia),以及什么是超文本(Hypertext)。其中超文本特有的优势是拥有超链接(Hyperlink)。如果我们把超链接引入到多媒体中,那就得到了超媒体(Hypermedia),因此这里的关键角色还是超链接。从HATEOAS的字面上进行理解,使用超媒体作为应用状态的引擎,指的就是应用的状态变更将由客户端访问不同的超媒体资源来驱动。
📌 HATEOAS的重要性在于打破了客户端和服务器之间严格的契约,使得客户端可以更加智能和自适应,而RESTful服务本身的演化和更新也变得更加容易。
📌 HATEOAS更多是一种概念,而HAL(Hypertext Application Language,超文本应用语言)是HATEOAS的一种实现方式。与普通的RESTful风格不同,对每个资源,HAL又将其细分成状态(State)、链接(Links)和子资源(Embedded Resource)三个标准部分
📌 HAL的出现主要弥补了普通JSON格式在API交互中的不足,让JSON更具有自描述性和导航性。
📌 RESTful API的第一个典型问题就是前端无法预判响应的数据格式
📌 一旦服务端对数据结构做了任何改变,前端都只能被动接收,而无法在发起请求之前感知到这种改变。
📌 RESTful API的第二个典型问题是无法根据请求控制对应的返回结果。
📌 前端请求可能只想获取User对象中的name和age字段,而不需要address字段。
📌 RESTful API的第四个典型问题,即请求地址过多的问题。如果针对各个具体场景我们都需要一一暴露专门的HTTP端点,那么在一个系统中HTTP端点数量会非常庞大,难以维护和管理
📌 相比于REST,GraphQL可以说是一个比较新的技术,它于2012年诞生在Facebook内部,并于2015年正式开源。顾名思义,GraphQL是一种基于图(Graph)的查询语言(Query Language,QL),从根本上改变了前后端交互API的定义和实现方式。
📌 针对RESTful API存在的多次请求问题,GraphQL可以把多次请求合并成一次。
📌 Schema首先,我们需要引入一个核心组件,即Schema。所谓Schema,简单讲就是一种前后端交互的协议和规范,或者可以把它类比成RESTful API中的接口定义文档。
📌 在Schema中,开发人员需要指定两部分内容。一方面,我们需要明确定义前后端交互的数据结构,包括具体的字段名称、类型、是否为空等属性。另一方面,GraphQL规定每一个Schema中可以存在一个根Query和根Mutation,分别用于执行查询和更新操作。
📌 DataFetcher组件的作用就是在执行查询时获取字段对应的数据。
📌 开发人员可以从DataFetchingEnvironment中获取传入的参数,并根据该参数来执行具体的数据查询操作
📌 通过Runtime Wiring机制,我们可以把DataFetcher整合在GraphQL的运行环境中
📌 开发人员本身并不需要了解这个GraphQlSource对象的构建过程,因为它的职责是在框架内部完成GraphQL执行引擎的初始化,这是Spring GraphQL框架自动会为我们做的事情。开发人员唯一要做的就是通过GraphQlSource获取一个GraphQL对象
📌 GraphQL引擎所需要执行的数据查询操作与业务相关,这部分功能需要开发人员根据具体业务场景进行设计并实现,这时候就会使用到graphql-spring-boot-starter中的RuntimeWiringBuilderCustomizer接口。RuntimeWiringBuilderCustomizer接口简化了Runtime-Wiring的实现过程,开发人员通过实现这个接口就可以设置一系列的DataFetcher
📌 首先,如果你在公开的Maven仓库中搜索graphql-spring-boot-starter这个artifactId,会发现存在多个对应的groupId,这是因为老版本的GraphQL Java Spring框架已经实现了同名的artifactId。而我们在这里指定groupId为org.springframework.experimental,这是Spring GraphQL框架目前所属的groupId,可以看到它还属于试验(experimental)阶段,并没有发布到公开的Maven仓库中。所以,为了引入这个依赖包,我们需要指定Spring官方的Maven仓库地址
📌 并不推荐你在任何场景下都使用GraphQL
📌 对于那些API定义与资源概念匹配度较高,也不需要实现类似在用户信息内部嵌套家庭成员信息的复杂查询场景,传统的RESTful API仍然是首选,各个HTTP端点之间相互独立,职责非常明确
📌 对于业务复杂度较高的场景,推荐使用GraphQL
📌 对于那些单一的RESTful服务,可以把GraphQL直接嫁接到已有的RESTful服务上
📌 RESTful服务中已经实现的业务逻辑层、数据访问层组件都可以得到复用,我们要做的只是开放一个新的GraphQL访问入口而已
📌 对于响应式编程而言,首先要明确的概念是数据流(Data Stream)
📌 所谓的流就是由生产者生产并由一个或多个消费者消费的元素序列。
📌 发布者(Publisher)是潜在的包含无限数量的有序元素的生产者,它根据收到的请求向当前订阅者发送元素。
📌 订阅者(Subscriber)从发布者那里订阅并接收元素。发布者向订阅者发送订阅令牌(Subscription Token)。
📌 处理器(Processor)充当订阅者和发布者之间的转换器(Transformer)。
📌 1)当发布者使用subscribe()方法实现对该发布者的订阅时,首先会创建一个具有相应逻辑的Subscription对象,这个Subscription对象定义了如何处理请求,以及如何发出数据。2)然后发布者将这个Subscription通过订阅者的onSubscribe()方法传给订阅者。3)在订阅者的onSubscribe()方法中,需要通过Subscription的request ()方法发起第一次请求。4)Subscription收到请求,就可以通过回调订阅者的onNext()方法发出元素,有多少发多少,但不能超过请求的个数。5)订阅者在onNext()方法中通常定义对元素的处理逻辑,处理完成之后,可以继续发起请求。6)发布者根据需要继续满足订阅者的请求。7)如果发布者的元素序列正常结束,就通过订阅者的onComplete()方法予以告知。如果序列发送过程中有错误,则通过订阅者的onError()方法予以告知并传递错误提示。这两种情况都会导致序列终止,订阅过程结束。
📌 Spring Cloud Gateway基于最新的Spring 5和Spring Boot 2以及用于响应式编程的Project Reactor框架,提供响应式、非阻塞式I/O模型。和其他API网关系统类似,Spring Cloud Gateway中的核心组件也是过滤器。
📌 过滤器用于在响应HTTP请求之前或之后修改请求本身及对应的响应结果。Spring Cloud Gateway提供了一个全局过滤器(GlobalFilter)的概念,对所有路由都生效。
📌 Spring WebFlux提供了完整的支持响应式开发的服务端技术栈。和Spring WebMVC相比,Spring WebFlux既支持基于@Controller、@RequestMapping等注解的传统开发模式,又支持基于Router Functions的函数式开发模式。
📌 Spring WebFlux则是构建在响应式流以及它的实现框架Reactor基础之上的一个开发框架,因此可以基于HTTP实现异步非阻塞的Web服务。
📌 在Spring WebMVC中,对Web请求的处理机制也基于管道-过滤器(Pipe-Filter)架构模式。Spring WebMVC使用了Servlet中的过滤器链(FilterChain)来对请求进行拦截
📌 当HTTP请求通过Servlet容器时就会被转换为一个ServletRequest对象,而处理的结果将以Servlet-Response对象的形式返回。当ServletRequest通过过滤器链中的一系列过滤器之后,最终就会到达作为前端控制器的DispatcherServlet。
📌 就整体架构而言,Spring WebFlux和Spring WebMVC本质上并没有什么区别。
📌 在WebFlux中,和DispatcherServlet相对应的组件是DispatcherHandler
📌 WebFlux同样实现了响应式版本的RequestMappingHandlerMapping和RequestMapping-HandlerAdapter,因此我们仍然可以采用注解的方法来构建Controller
📌 WebFlux还提供了RouterFunctionMapping和HandlerFunctionAdapter组合,专门用来提供基于函数式编程的开发模式。
📌 使用函数式编程模型创建响应式Web API时,我们需要引入一组全新的编程对象,即ServerRequest、ServerResponse、HandlerFunction和RouterFunction。
📌 相比retrieve()方法,exchange()方法对响应结果拥有更多的控制权
📌 RSocket协议诞生于2015年,是一个与语言无关的二进制网络协议,用来解决单一的请求-响应模式以及现有网络传输协议所存在的问题
📌 RSocket以异步消息的方式提供了四种交互模式,除了请求-响应(request/response)模式之外,还包括请求-响应流(request/stream)、即发-即忘(fire-and-forget)和通道(channel)这三种新的交互模式
📌 请求-响应模式:这是最典型也最常见的交互模式。发送方在发送消息给接收方之后,等待与之对应的响应消息。
📌 请求-响应流模式:发送方的每个请求消息,都对应于接收方的一个消息流作为响应。
📌 即发-即忘模式:发送方的请求消息没有与之对应的响应。
📌 通道模式:在发送方和接收方之间建立一个双向传输的通道。
📌 @MessageMapping是Spring提供的,用来指定WebSocket、RSocket等协议中消息处理的目的地。
📌 R2DBC是Reactive Relational DataBase Connectivity的简称,即响应式关系数据库连接。该规范允许驱动程序提供与数据库的完全响应式和非阻塞集成
📌 @Cacheable注解可以标记在一个方法上,也可以标记在一个类上。当标记在一个类上时,该类所有的方法都是支持缓存的。
📌 @CachePut注解也可以声明在一个方法中来启用缓存功能。与@Cacheable注解不同的是,添加了@CachePut注解的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果存入指定的缓存中。显然,从命名上看,@CachePut适合于执行更新操作的方法。
📌 @CacheEvict可以指定的属性也包括value、key和condition。但因为清除缓存的操作可能涉及多个元素,所以@CacheEvict注解额外提供了一个allEntries属性来指定是否清除缓存中的所有元素。这个属性的默认值是false。而当指定了allEntries属性为true时,Spring将忽略指定的key,并清除缓存中的所有元素。
📌 在Spring缓存中,缓存键的生成有两种策略,一种是默认策略,另一种是自定义策略。
📌 。除了上述将方法参数作为key之外,Spring还为我们提供了一个Root对象来生成key。通过该Root对象,开发人员可以获取到本地方法调用所涉及的一组元数据
📌 如果没有指定key属性,那么Spring会帮我们自动生成键。默认的键生成策略是通过KeyGenerator生成的
📌 默认键生成策略的具体运作方式是这样的:如果方法没有参数,则使用0作为key;如果只有一个参数,则使用该参数作为key;如果参数多于一个的话则使用所有参数的HashCode作为key。
📌 在注解模式下想要启用Spring内置的缓存功能,需要在配置类上添加@EnableCaching
📌 除了基于Java API的ConcurrentMapCacheManager之外,EhCache、Redis、Caffeine、Guava等第三方缓存工具都已经被整合进了Spring框架
📌 对于缓存而言,其核心思想是在调用一个缓存方法时把该方法的参数和返回结果作为一个键值对存放在缓存中,等到下次基于同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果并进行返回。
📌 在对缓存机制的整体设计上,Spring采用了典型的两层架构,即内核层和扩展层。所谓内核层,相当于对缓存本身的一种抽象,抽取了与缓存相关的最核心的操作方法;
📌 扩展层,则是基于内核层的抽象,分别集成业界主流的缓存工具,从而对缓存的核心操作方法提供实现方案。
📌 答案:Spring为开发人员提供的缓存注解数量并不多,比较容易让人理解和记忆。日常开发过程中比较常用的缓存注解包括@Cacheable注解、@CachePut注解和@CacheEvict注解,这三个注解分别对应查询、更新和删除操作
📌 Spring Security中所采用的最基本的架构就是管道-过滤器架构模式。
📌 在Web应用程序开发过程中,客户端访问RESTful服务端的过程应该是无状态的。如果不配置为无状态,则服务端会堆积海量的Session ID,导致出现性能问题
📌 Spring Security中的认证过程由一组核心对象组成,这些对象可以分成两大类,一类是用户对象,另一类是认证对象。
📌 JdbcTemplate正是基于模板方法模式和回调机制解决了原生JDBC面临的复杂性问题。
📌 Fetch Size可以用来指定一次从数据库中检索的行数,而大多数JDBC驱动程序的默认值是10
📌 Fetch Size不应该采用硬编码,而需要确保它的可配置性,因为它影响到JVM堆内存大小,不同的环境会有所不同。
📌 在大多数标准JDBC API中,默认的提交模式是自动提交(Auto Commit)
📌 Spring Data基于Repository架构模式抽象出一套实现该模式的统一数据访问方式。
📌 JPA全称是Java Persistence API,即Java持久化API,是一个Java应用程序接口规范,充当面向对象的领域模型和关系数据库系统之间的桥梁,所以属于一种ORM技术。
📌 所谓延迟加载(Lazy Load,有时也称为懒加载)是指在进行表的关联查询时,按照设置的延迟规则推迟对关联对象的查询
📌 二级缓存是与命名空间(namespace)强关联的,即如果在不同的命名空间下存在相同的查询SQL,这两者之间也是不共享缓存数据的
📌 在Spring Data JPA中,解决N+1问题的方式也很明确,就是使用JOIN FETCH机制。JOIN FETCH机制会强制Spring Data JPA在处理关联对象时使用INNER JOIN语句来执行关联查询,从而使用一条SQL语句完成对所有对象的查询。
📌 方法的实际执行过程将提交给Spring的任务执行器TaskExecutor进行执行。
📌 一方面,@Async注解的运行过程依赖于Spring中对Bean生命周期的处理;另一方面,我们也需要充分利用基于代理的拦截器机制来实现异步操作
📌 在Spring中,BeanPostProcessor的作用是在完成Bean实例化和依赖注入之后,在显式调用初始化方法的前后添加我们自己的逻辑,可以认为这里是添加代理机制的绝佳位置
📌 相较于@Async注解,WebAsyncTask为开发人员提供了更灵活的异步任务处理机制,并内置了异步回调、超时处理和异常处理等功能特性。
📌 在日常Web开发过程中,常见的异步处理需求来自三方面的场景,包括常规的异步请求处理过程、不需要返回值的即发-即忘处理过程,以及耗时较长的大数据量请求处理。
📌 所谓的Executor,本质上是在所有内部任务线程上提供了一个抽象层,从而管理线程的整个并发执行流。
📌 Java并发API支持固定线程池(Fixed-Thread Pool)、缓存线程池(Cached-Thread Pool)、单线程池(Single-Thread Pool)和Fork/Join线程池等多种线程池类型。
📌 限流器的作用是在线程执行的并发度达到阈值时让后续的线程处于阻塞等待。
📌 pring Cloud中的Spring Cloud Config框架,该框架的客户端自动更新机制就依赖于Actuator的/actuator/bus-refresh端点
📌 Spring Cloud Bus是Spring Cloud中用于实现消息总线的专用组件,集成了RabbitMQ、Kafka等主流消息中间件
📌 Spring Cloud Config集成Spring Cloud Bus的目的就是想借助它的消息通信能力。
📌 Spring Boot Admin组件并不是Spring家族官方提供的组件,而是来自一个叫作codecentric AG的团队。想要把普通的Spring Boot应用程序转变为Spring Boot Admin Server,只需要在Bootstrap类上添加一个注解即可,这个注解就是@EnableAdminServer。
📌 @SpringBootApplication注解实际上由三个注解组合而成,分别是@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。
📌 根据所引入类的不同类型,Spring容器对@Import注解有以下四种处理方式。■如果该类实现了ImportSelector接口,Spring容器就会实例化该类,并且调用其selectImports()方法完成类的导入。■如果该类实现了DeferredImportSelector接口,则Spring容器也会实例化该类并调用其selectImports()方法。DeferredImportSelector继承了ImportSelector,区别在于DeferredImportSelector实例的selectImports()方法的调用时机晚于ImportSelector实例,要等到@Configuration注解中相关的业务全部都处理完了才会调用。■如果该类实现了ImportBeanDefinitionRegistrar接口,Spring容器就会实例化该类,并且调用其registerBeanDefinitions()方法。■如果该类没有实现上述三种接口中的任何一个,Spring容器就会直接实例化该类。
📌 JDK提供了一个工具类java.util.ServiceLoader来实现SPI机制,该类用于实现服务查找和加载。当服务提供者提供了服务接口的一种实现之后,我们可以在JAR包的META-INF/services/目录下创建一个以该服务接口命名的文件,并在这个文件中配置一组Key-Value,用于指定服务接口与其具体实现类的映射关系。当外部程序装配这个JAR包时,它就能通过该JAR包META-INF/services/目录中的配置文件找到具体的实现类名,并装载实例化,从而完成目标服务的注入。
📌 Spring Native原生镜像的启动速度非常快,通常不会超过100ms,相比于传统模式下的启动时间可以说是瞬时的启动。同时,Spring Native原生镜像在运行时也具备更低的资源消耗。
📌 该方法可以实现按需注入,帮助我们只在需要时注入依赖关系。
📌 一旦采用构造器注入,在Spring项目启动的时候,就会抛出一个循环依赖异常,从而提醒你避免使用循环依赖。
性能上JDK动态代理比Cglib略有优势,但是不用太过于关注,因为差别不大
📌 @Transactional
📌 这里我们直接从AopContext中获取代理对象。
📌 RESTful API的第三个典型问题就是多次请求。
📌 Spring缓存组件的核心优势在于设计并实现了一个抽象层,从而为开发人员提供了统一的缓存使用API
⏱ 2023-05-01 17:01:29